package service;

import annotation.DependencyInjection;
import annotation.Singleton;
import entity.Group;
import entity.GroupMessage;
import entity.GroupRelation;
import entity.User;
import enums.ResponseErrorCode;
import exception.*;
import repository.GroupMessageRepository;
import repository.GroupRelationRepository;
import repository.GroupRepository;
import repository.UserRepository;
import response.group.GroupCreateResponse;
import response.group.GroupGetResponse;
import response.group.message.GroupMessageListResponse;
import response.group.message.GroupMessagePullResponse;
import response.group.message.GroupMessageSendResponse;
import response.group.relation.GroupListResponse;
import response.group.relation.GroupMemberListResponse;
import response.group.relation.GroupRelationBuildResponse;
import response.group.relation.GroupRelationDestroyResponse;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

@Singleton
public class GroupService {
    private UserRepository userRepository;
    private GroupRepository groupRepository;
    private GroupMessageRepository groupMessageRepository;
    private GroupRelationRepository groupRelationRepository;

    @DependencyInjection
    public GroupService(UserRepository userRepository, GroupRepository groupRepository, GroupMessageRepository groupMessageRepository, GroupRelationRepository groupRelationRepository) {
        this.userRepository = userRepository;
        this.groupRepository = groupRepository;
        this.groupMessageRepository = groupMessageRepository;
        this.groupRelationRepository = groupRelationRepository;
    }

    public GroupCreateResponse createGroup(Group group) {
        userRepository.startRead();
        groupRepository.startWrite();
        try {
            User user = userRepository.getByUsername(group.getMasterUsername());
            if (user == null) {
                throw new ServerException(ResponseErrorCode.USER_NOT_EXISTED);
            }
            groupRepository.add(group);
            return new GroupCreateResponse();
        } finally {
            userRepository.endRead();
            groupRepository.endWrite();
        }
    }

    public GroupGetResponse getGroup(UUID groupId) {
        groupRepository.startRead();
        try {
            Group group = groupRepository.getByUuid(groupId);
            if (group == null) {
                throw new ServerException(ResponseErrorCode.GROUP_NOT_EXISTED);
            }
            return new GroupGetResponse(group);
        } finally {
            groupRepository.endRead();
        }
    }

    public GroupListResponse getListByUsername(String username) {
        groupRepository.startRead();
        groupRelationRepository.startRead();
        try {
            List<GroupRelation> groupRelationList = groupRelationRepository.getByUsername(username);
            List<Group> groupList = groupRelationList.stream().map(groupRelation -> groupRepository.getByUuid(groupRelation.getGroupId())).collect(Collectors.toList());
            return new GroupListResponse(groupList);
        } finally {
            groupRepository.endRead();
            groupRelationRepository.endRead();
        }
    }

    public GroupMessageListResponse getGroupMessageList(UUID groupId) {
        groupMessageRepository.startRead();
        try {
            List<GroupMessage> groupMessageList = groupMessageRepository.getByUuid(groupId);
            return new GroupMessageListResponse(groupMessageList);
        } finally {
            groupMessageRepository.endRead();
        }
    }

    public GroupMessagePullResponse getLatestMessagesByUsername(UUID groupId, String username) {
        groupRelationRepository.startWrite();
        groupMessageRepository.startRead();
        try {
            GroupRelation groupRelation = groupRelationRepository.get(groupId, username);
            if (groupRelation == null) {
                throw new ServerException(ResponseErrorCode.GROUP_RELATION_NOT_EXISTED);
            }
            LocalDateTime lastTime = groupRelation.getTimestamp();
            LocalDateTime now = LocalDateTime.now();
            List<GroupMessage> groupMessageList = groupMessageRepository.get(groupId, lastTime, now);
            groupMessageList = groupMessageList.stream().filter(groupMessage -> !Objects.equals(groupMessage.getSenderUsername(), username)).collect(Collectors.toList());
            groupRelationRepository.updateTimestamp(groupId, username, now);
            return new GroupMessagePullResponse(groupMessageList);
        } finally {
            groupRelationRepository.endWrite();
            groupMessageRepository.endRead();
        }
    }

    public GroupMessageSendResponse sendMessage(GroupMessage groupMessage) {
        groupRelationRepository.startRead();
        groupMessageRepository.startWrite();
        try {
            GroupRelation groupRelation = groupRelationRepository.get(groupMessage.getGroupId(), groupMessage.getSenderUsername());
            if (groupRelation == null) {
                throw new ServerException(ResponseErrorCode.GROUP_RELATION_NOT_EXISTED);
            }
            groupMessage.setTime(LocalDateTime.now()); // clock synchronization
            groupMessageRepository.add(groupMessage);
            return new GroupMessageSendResponse();
        } finally {
            groupRelationRepository.endRead();
            groupMessageRepository.endWrite();
        }
    }

    public GroupRelationBuildResponse addGroupRelation(UUID groupId, String username) {
        groupRelationRepository.startWrite();
        try {
            if (groupRelationRepository.get(groupId, username) != null) {
                throw new ServerException(ResponseErrorCode.GROUP_RELATION_ALREADY_EXISTED);
            }
            groupRelationRepository.add(new GroupRelation(username, groupId, LocalDateTime.MIN));
            return new GroupRelationBuildResponse();
        } finally {
            groupRelationRepository.endWrite();
        }
    }

    public GroupRelationDestroyResponse removeGroupRelation(UUID groupId, String username) {
        groupRelationRepository.startWrite();
        try {
            if (groupRelationRepository.get(groupId, username) == null) {
                throw new ServerException(ResponseErrorCode.GROUP_RELATION_NOT_EXISTED);
            }
            groupRelationRepository.remove(groupId, username);
            return new GroupRelationDestroyResponse();
        } finally {
            groupRelationRepository.endWrite();
        }
    }

    public GroupMemberListResponse groupMemberList(UUID groupId) {
        groupRelationRepository.startRead();
        try {
            List<GroupRelation> groupRelationList = groupRelationRepository.getByGroupId(groupId);
            List<String> memberUsernameList = groupRelationList.stream().map(GroupRelation::getUsername).collect(Collectors.toList());
            return new GroupMemberListResponse(memberUsernameList);
        } finally {
            groupRelationRepository.endRead();
        }
    }
}
